/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#ifndef ARMV8_A_PAGETABLE_H
#define ARMV8_A_PAGETABLE_H

#include <stdlib/stdint.h>
#include <stdlib/string.h>
#include <tools/macros.h>

#define INNER_SHAREABLE (0x3)

#define MMU_MAIR_ATTR1 (0x04 << (8 * 1))
#define MMU_MAIR_ATTR2 (0xff << (8 * 2))
#define MMU_MAIR_ATTR3 (0x44 << (8 * 3))

#define NORMAL_MEMORY (0x2)
#define DEVICE_MEMORY (0x1)

#define AARCH64_PTE_UX  (0x0LU)
#define AARCH64_PTE_UXN (0x1LU)
#define AARCH64_PTE_PXN (0x1LU)

#define AARCH64_PTE_AF_ACCESSED (1)

#define AARCH64_PTE_GLOBAL  (0)
#define AARCH64_PTE_NGLOBAL (1)

#define PAGE_ORDER (9)
#define PAGE_SHIFT (12)
#define PTP_INDEX_MASK ((1 << (PAGE_ORDER)) - 1)
#define PTP_ENTRIES (1UL << PAGE_ORDER)

#define PGD_SHIFT ((3 * PAGE_ORDER) + PAGE_SHIFT)
#define PUD_SHIFT ((2 * PAGE_ORDER) + PAGE_SHIFT)
#define PMD_SHIFT ((1 * PAGE_ORDER) + PAGE_SHIFT)

#define PGD_SIZE  (1UL << PGD_SHIFT)
#define PUD_SIZE  (1UL << PUD_SHIFT)
#define PMD_SIZE  (1UL << PMD_SHIFT)
#define PAGE_SIZE (1UL << PAGE_SHIFT)

#define PGD_MASK   (PGD_SIZE - 1)
#define PUD_MASK   (PUD_SIZE - 1)
#define PMD_MASK   (PMD_SIZE - 1)
#define PAGE_MASK  (PAGE_SIZE - 1)

#define GET_PGD_INDEX(addr) ((addr >> PGD_SHIFT) & PTP_INDEX_MASK)
#define GET_PUD_INDEX(addr) ((addr >> PUD_SHIFT) & PTP_INDEX_MASK)
#define GET_PMD_INDEX(addr) ((addr >> PMD_SHIFT) & PTP_INDEX_MASK)
#define GET_PAGE_INDEX(addr) ((addr >> PAGE_SHIFT) & PTP_INDEX_MASK)

#define GET_VA_OFFSET_PUD(va) (va & PUD_MASK)
#define GET_VA_OFFSET_PMD(va) (va & PMD_MASK)
#define GET_VA_OFFSET_PAGE(va) (va & PAGE_MASK)

#define ALIGN_UP(addr, size)   (((addr) + ((size)-1)) & (~((typeof(addr))(size)-1)))
#define ALIGN_DOWN(addr, size) ((addr) & (~((typeof(addr))(size)-1)))

#define PAGE_ALIGN_UP(addr)   ALIGN_UP(addr, PAGE_SIZE)
#define PAGE_ALIGN_DOWN(addr) ALIGN_DOWN(addr, PAGE_SIZE)

#define lowbitsmask_off(bits, off) (lowbitsmask(bits) << (off))

#define PTE_BIT_READ(length, off, pte) (((pte) >> (off)) & lowbitsmask(length))

#define PTE_BIT_WRITE(length, off, pte, val) do {   \
    (pte) &= (~lowbitsmask_off((length), (off)));         \
    (pte) |= ((val) << (off));                            \
} while (0)

#define TABLE_SHIFT     (PAGE_SHIFT)

#define PTE_LOWATTR_READ_VALID(pte)     (PTE_BIT_READ(1,  0, (pte))) // any
#define PTE_LOWATTR_READ_TABLE(pte)     (PTE_BIT_READ(1,  1, (pte))) // any

#define PTE_LOWATTR_WRITE_VALID(pte, val)       PTE_BIT_WRITE(1,  0, (pte), (val)) // any
#define PTE_LOWATTR_WRITE_TABLE(pte, val)       PTE_BIT_WRITE(1,  1, (pte), (val)) // any
#define PTE_LOWATTR_WRITE_ATTRINDEX(pte, val)   PTE_BIT_WRITE(3,  2, (pte), (val)) // L1block L2block page
#define PTE_LOWATTR_WRITE_AP(pte, val)          PTE_BIT_WRITE(2,  6, (pte), (val)) // L1block L2block page
#define PTE_LOWATTR_WRITE_SH(pte, val)          PTE_BIT_WRITE(2,  8, (pte), (val)) // any
#define PTE_LOWATTR_WRITE_AF(pte, val)          PTE_BIT_WRITE(1, 10, (pte), (val)) // any
#define PTE_LOWATTR_WRITE_NG(pte, val)          PTE_BIT_WRITE(1, 11, (pte), (val)) // L1block L2block page 
                                                                                   
#define PTE_LOWATTR_READ_AP(pte)                PTE_BIT_READ(2,  6, (pte)) // L1block L2block page
#define PTE_LOWATTR_READ_NS(pte)                PTE_BIT_READ(1,  5, (pte)) // L1block L2block page
#define PTE_LOWATTR_READ_SH(pte)                PTE_BIT_READ(2,  8, (pte)) // any
#define PTE_LOWATTR_READ_AF(pte)                PTE_BIT_READ(1,  10, (pte)) // any
#define PTE_LOWATTR_READ_NG(pte)                PTE_BIT_READ(1, 11, (pte)) // L1block L2block page 
#define PTE_LOWATTR_READ_nT(pte)                PTE_BIT_READ(1, 16, (pte)) // L1block L2block page 
#define PTE_LOWATTR_READ_ATTRINDEX(pte)         PTE_BIT_READ(3,  2, (pte)) // L1block L2block page

#define PTE_UPATTR_WRITE_PXN(pte, val)       PTE_BIT_WRITE(1, 5 + 48, pte, val) // L1block L2block page
#define PTE_UPATTR_WRITE_UXN(pte, val)       PTE_BIT_WRITE(1, 6 + 48, pte, val) // L1block L2block page
                                                                                
#define PTE_UPATTR_READ_PXN(pte)             PTE_BIT_READ(1, 5 + 48, pte) // L1block L2block page
#define PTE_UPATTR_READ_UXN(pte)             PTE_BIT_READ(1, 6 + 48, pte)
#define PTE_UPATTR_READ_GP(pte)              PTE_BIT_READ(1, 2 + 48, pte)
#define PTE_UPATTR_READ_DBM(pte)             PTE_BIT_READ(1, 3 + 48, pte)
#define PTE_UPATTR_READ_Cont(pte)            PTE_BIT_READ(1, 4 + 48, pte)
#define PTE_UPATTR_READ_PBHA(pte)            PTE_BIT_READ(3, 11 + 48, pte)

#define IS_PTE_INVALID(pte)                  (0U == PTE_LOWATTR_READ_VALID(pte))
#define IS_PTE_TABLE(pte)                    (0U != PTE_LOWATTR_READ_TABLE(pte))
                                                                                  
#define PTE_TABLE_READ_ADDR(pte)        (PTE_BIT_READ(48 - TABLE_SHIFT, TABLE_SHIFT, (pte)))


typedef uint64_t pte_t;

typedef struct page_table {
    pte_t entry[PTP_ENTRIES];
} page_table_t ALIGNED(PAGE_SIZE);


static inline void pte_write_base(uint64_t* pte, uint64_t base, uint32_t shift) {
    uint64_t tobase = base & lowbitsmask_off(48U - shift, shift);
    (*pte) &= (~lowbitsmask_off(48U - shift, shift));
    (*pte) |= tobase;
}

static inline uint64_t pte_read_base(uint64_t* pte, uint32_t shift) {
    uint64_t p = *pte;
    p &= lowbitsmask_off(48U - shift, shift) ;
    return p;
}

int  page_table_recycle(page_table_t *pd, int level);
void page_table_copy(page_table_t *des, page_table_t *src, int level);
uint64_t page_table_missing_pt(page_table_t *pd, uint64_t vbase, uint64_t size, uint64_t level);
int      page_table_map(page_table_t *pd, uint64_t pbase, uint64_t vbase, uint64_t size, uint64_t attr);
int      page_table_unmap(page_table_t *pd, uint64_t va, size_t len);
void     page_table_init(page_table_t *pd);

void page_table_switch_to(page_table_t *pd, int iskernel);
void page_table_switch_to_asid(page_table_t *pd, unsigned short asid, int iskernel);

void     page_table_active(page_table_t *pd, int iskernel);
int      verify_va_to_pa(page_table_t *pgtbl, uint64_t va, uint64_t t_pa);
uint64_t trans_va_into_pa(page_table_t *pgtbl, uint64_t va);

int      split_block_into_table(page_table_t *pgtbl, pte_t *cur_pte, uint64_t va, size_t len, int level);
uint64_t get_attr_from_pte(pte_t *pte);
int get_next_ptp(page_table_t *cur_ptp, uint8_t level, uint64_t va, page_table_t **next_ptp, pte_t **pte, bool_t alloc);
#endif
